#ifndef __TCPTUNNEL_H__
#define __TCPTUNNEL_H__

namespace encrypted
{
    namespace tunnel
    {

        typedef boost::asio::ip::tcp::socket TcpSocket;
        typedef boost::shared_ptr<TcpSocket> TcpSocketPtr;

// Receive data from peerin,
// use the crypto to encrypt or decrypt the data,
// put the send action to the send proxy(such as BandwithLimitSendProxy)
// Can be replaced by another tunnel(such as UdpTunnel when do the NAT traverse)
        template<typename CryptoPtr, typename SendProxyPtr>
        class TcpTunnel
        {
        public:
            TcpTunnel(TcpSocketPtr peerin,
                      TcpSocketPtr peerout,
                      CryptoPtr crypto,
                      SendProxyPtr sendProxy)
                : peerin(peerin),
                  peerout(peerout),
                  crypto(crypto),
                  sendProxy(sendProxy),
                  cryptoBuf(boost::shared_array<char>(new char[crypto->cryptedLength(buf.size())]))
            {
            }

            ~TcpTunnel()
            {
                peerin->close();
                peerout->close();
            }

            // Start to receive data asynchronize
            void start()
            {
                // Asynchronize receive data from socket 'peerin'.
                // This function will return immediately.
                // The callback function 'TcpTunnel::received' will be called when data arrived.
                peerin->async_receive(boost::asio::buffer(buf, buf.size()),
                                      boost::bind(&TcpTunnel::received, this, _1, _2));
            }

        private:
#ifdef WIN32
            friend typedef SendProxyPtr::element_type;
#else
            friend class SendProxyPtr::element_type;
#endif
            // Callback.  Will be called when data arrived.
            void received(const boost::system::error_code& error, std::size_t bytes_transferred)
            {
                if(error)
                {
                    delete this;
                    return;
                }
                size_t cryptedLength = crypto->cryptedLength(bytes_transferred);

                // Encrypt or Decrypt data
                crypto->crypt(cryptoBuf, buf, bytes_transferred);

                // Use the send proxy to send data.
                // The send proxy can be bandwith limited send proxy to limit the max bandwith of the encryptedtunnel will be used.
                sendProxy->send(peerout, cryptoBuf.get(), cryptedLength, this);
            }

            // Callback.  Will be called when data send complete
            void sent(const boost::system::error_code& error, std::size_t bytes_transferred)
            {
                if(error)
                {
                    delete this;
                    return;
                }
                start();
            }

            TcpSocketPtr peerin;
            TcpSocketPtr peerout;
            CryptoPtr crypto;
            boost::array<char, 10240> buf;
            boost::shared_array<char> cryptoBuf;
            SendProxyPtr sendProxy;
        };
    }
}
#endif
